package cn.iocoder.yudao.module.member.assets.service;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.extra.spring.SpringUtil;
import cn.iocoder.yudao.framework.business.basic.dao.query.CoreQueryWrapper;
import cn.iocoder.yudao.framework.business.basic.service.impl.BaseService;
import cn.iocoder.yudao.framework.common.exception.Assertions;
import cn.iocoder.yudao.module.api.ad.effect.AdEffectRecordApi;
import cn.iocoder.yudao.module.api.ad.effect.enums.AdEffectSceneEnum;
import cn.iocoder.yudao.module.api.ad.effect.enums.AdEffectTypeEnum;
import cn.iocoder.yudao.module.api.ad.plan.AdPlanApi;
import cn.iocoder.yudao.module.api.ad.plan.dto.AdPlanRespDTO;
import cn.iocoder.yudao.module.api.infrastructure.config.ConfigApiV2;
import cn.iocoder.yudao.module.api.infrastructure.config.dto.AdConfigRespDTO;
import cn.iocoder.yudao.module.api.member.assets.MemberDigitalAssetsApi;
import cn.iocoder.yudao.module.api.member.task.enums.TaskStatusEnum;
import cn.iocoder.yudao.module.api.pay.bill.PayBillApi;
import cn.iocoder.yudao.module.api.pay.bill.dto.PayBillCreateDTO;
import cn.iocoder.yudao.module.api.pay.bill.enums.BillCategoryEnum;
import cn.iocoder.yudao.module.api.pay.bill.enums.BillTypeEnum;
import cn.iocoder.yudao.module.member.assets.bo.CryptocurrencyIncreasingTaskCountBO;
import cn.iocoder.yudao.module.member.assets.bo.CryptocurrencyIncreasingTaskSimpleBO;
import cn.iocoder.yudao.module.member.assets.convert.CryptocurrencyIncreasingTaskConvert;
import cn.iocoder.yudao.module.member.assets.dal.dataobject.CryptocurrencyIncreasingTaskDO;
import cn.iocoder.yudao.module.member.assets.dal.dataobject.CryptocurrencyIncreasingTaskRecordDO;
import cn.iocoder.yudao.module.member.assets.dal.mysql.CryptocurrencyIncreasingTaskMapper;
import cn.iocoder.yudao.module.member.assets.dal.mysql.CryptocurrencyIncreasingTaskRecordMapper;
import cn.iocoder.yudao.module.member.assets.vo.AppCryptocurrencyIncreasingTakeAdRespVO;
import cn.iocoder.yudao.module.member.assets.vo.AppCryptocurrencyIncreasingTaskQueryDTO;
import cn.iocoder.yudao.module.member.assets.vo.AppCryptocurrencyIncreasingTaskSubmitVO;
import cn.iocoder.yudao.module.member.assets.vo.AppCryptocurrencyIncreasingTaskVO;
import jakarta.annotation.Nonnull;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.validation.annotation.Validated;

import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.List;
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

import static cn.iocoder.yudao.module.api.infrastructure.ConfigConstants.CONFIG_AD;

/**
 * 联盟豆增值任务 Service 实现类
 *
 * @author 山野羡民
 */
@Slf4j
@Service
@Validated
public class AppCryptocurrencyIncreasingTaskServiceImpl extends BaseService<CryptocurrencyIncreasingTaskDO, AppCryptocurrencyIncreasingTaskVO, AppCryptocurrencyIncreasingTaskQueryDTO> implements AppCryptocurrencyIncreasingTaskService {
    @Resource
    private CryptocurrencyIncreasingTaskMapper cryptocurrencyIncreasingTaskMapper;
    @Resource
    private CryptocurrencyIncreasingTaskRecordMapper cryptocurrencyIncreasingTaskRecordMapper;

    @Resource
    @Lazy // 延迟加载，避免循环依赖
    private AdEffectRecordApi adEffectRecordApi;
    @Resource
    @Lazy // 延迟加载，避免循环依赖
    private AdPlanApi adPlanApi;
    @Resource
    @Lazy // 延迟，避免循环依赖报错
    private MemberDigitalAssetsApi memberDigitalAssetsApi;
    @Resource
    @Lazy // 延迟，避免循环依赖报错
    private PayBillApi payBillApi;
    @Resource
    @Lazy // 延迟，避免循环依赖报错
    private ConfigApiV2 configApiV2;

    private final Lock lock = new ReentrantLock();

    @Override
    protected CryptocurrencyIncreasingTaskMapper getInjectedMapper() {
        return cryptocurrencyIncreasingTaskMapper;
    }

    @Nonnull
    @Override
    protected CoreQueryWrapper<CryptocurrencyIncreasingTaskDO> buildQueryWrapper(AppCryptocurrencyIncreasingTaskQueryDTO param) {
        CoreQueryWrapper<CryptocurrencyIncreasingTaskDO> wrapper = super.buildQueryWrapper(param);
        wrapper.eqIfPresent("status", param.getStatus());
        wrapper.orderByDesc("update_time");
        return wrapper;
    }

    @Override
    public AppCryptocurrencyIncreasingTaskVO convertToViewObject(CryptocurrencyIncreasingTaskDO entity) {
        return CryptocurrencyIncreasingTaskConvert.INSTANCE.convert2(entity);
    }

    @Override
    public Long create(Integer inAmount, Double incrementPercent, Integer requireDays, Integer requireTasks, Long userId) {
        int outAmount = (int) (inAmount + inAmount * incrementPercent / 100);
        CryptocurrencyIncreasingTaskDO entity = new CryptocurrencyIncreasingTaskDO();
        entity.setInAmount(inAmount);
        entity.setIncrementPercent(incrementPercent);
        entity.setOutAmount(outAmount);
        entity.setRequireDays(requireDays);
        entity.setCompleteDays(0);
        entity.setRequireDailyTasks(requireTasks);
        entity.setUserId(userId);
        entity.setExpireTime(LocalDateTime.now().plusDays(requireDays));
        entity.setStatus(TaskStatusEnum.PENDING.getValue());
        getInjectedMapper().insert(entity);
        return entity.getId();
    }

    @Override
    public AppCryptocurrencyIncreasingTaskVO getAvailable(Long userId) {
        List<CryptocurrencyIncreasingTaskDO> list = getInjectedMapper().selectListAvailableByUserId(userId);
        Assertions.notEmpty(list, "没有可用的联盟豆增值任务");
        List<CryptocurrencyIncreasingTaskDO> updateBatch = list.stream()
                .filter(it -> Objects.equals(it.getStatus(), TaskStatusEnum.PENDING.getValue()))
                .peek(it -> it.setStatus(TaskStatusEnum.DOING.getValue()))
                .toList();
        if (CollUtil.isNotEmpty(updateBatch)) {
            getInjectedMapper().updateBatch(updateBatch);
        }
        // 同一个用户的多个联盟豆增值任务合并为一个
        CryptocurrencyIncreasingTaskDO merge = new CryptocurrencyIncreasingTaskDO();
        merge.setUserId(userId);
        merge.setInAmount(list.stream().mapToInt(CryptocurrencyIncreasingTaskDO::getInAmount).sum());
        merge.setOutAmount(list.stream().mapToInt(CryptocurrencyIncreasingTaskDO::getOutAmount).sum());
        merge.setIncrementPercent(list.stream().mapToDouble(CryptocurrencyIncreasingTaskDO::getIncrementPercent).max().orElse(0d));
        merge.setRequireDays(list.stream().mapToInt(CryptocurrencyIncreasingTaskDO::getRequireDays).max().orElse(0));
        merge.setCompleteDays(list.stream().mapToInt(CryptocurrencyIncreasingTaskDO::getCompleteDays).min().orElse(0));
        merge.setRequireDailyTasks(list.stream().mapToInt(CryptocurrencyIncreasingTaskDO::getRequireDailyTasks).max().orElse(0));
        merge.setExpireTime(list.stream().map(CryptocurrencyIncreasingTaskDO::getExpireTime).reduce((dt1, dt2) -> dt2.isAfter(dt1) ? dt2 : dt1).orElse(LocalDateTime.now()));
        merge.setStatus(list.stream().mapToInt(CryptocurrencyIncreasingTaskDO::getStatus).min().orElse(TaskStatusEnum.PENDING.getValue()));
        int minCount = merge.getRequireDailyTasks();
        for (CryptocurrencyIncreasingTaskDO it : list) {
            CryptocurrencyIncreasingTaskRecordDO record = cryptocurrencyIncreasingTaskRecordMapper.getOrCreateToday(userId, it.getId());
            if (record.getCount() < minCount) {
                minCount = record.getCount();
            }
        }
        AppCryptocurrencyIncreasingTaskVO respVO = CryptocurrencyIncreasingTaskConvert.INSTANCE.convert2(merge);
        respVO.setCompleteDailyTasks(minCount);
        return respVO;
    }

    @Transactional(rollbackFor = Exception.class)
    @Override
    public AppCryptocurrencyIncreasingTakeAdRespVO takeAd(Long userId) {
        Set<Long> todayCompletePlanIds = adEffectRecordApi.getTodayCompletePlanIds(userId);
        AdPlanRespDTO adPlan = adPlanApi.takeAvailable(todayCompletePlanIds);
        if (CollUtil.isNotEmpty(todayCompletePlanIds) && adPlan == null) {
            // 全部广告已经看完了，则重复取出来继续看
            adPlan = adPlanApi.takeAvailable(null);
        }
        Assertions.notNull(adPlan, "领取任务失败：没有可看的广告了");
        AppCryptocurrencyIncreasingTakeAdRespVO respVO = new AppCryptocurrencyIncreasingTakeAdRespVO();
        respVO.setPlayMinSeconds(configApiV2.getConfig(CONFIG_AD, AdConfigRespDTO.class).getPlayMinSeconds());
        adEffectRecordApi.createIfNecessary(userId, AdEffectSceneEnum.CRYPTOCURRENCY_INCREMENT_SIGN, AdEffectTypeEnum.EXPOSURE, adPlan.getId(), 0, 0);
        respVO.setAdvertising(adPlan);
        return respVO;
    }

    @Transactional(rollbackFor = Exception.class)
    @Override
    public void submit(AppCryptocurrencyIncreasingTaskSubmitVO param, Long userId) {
        Long taskId = param.getTaskId();
        List<CryptocurrencyIncreasingTaskDO> list;
        if (taskId == null) {
            list = getInjectedMapper().selectListAvailableByUserId(userId);
            Assertions.notEmpty(list, "联盟豆增值任务不存在");
        } else {
            CryptocurrencyIncreasingTaskDO entity = getInjectedMapper().selectById(taskId);
            Assertions.notNull(entity, "联盟豆增值任务不存在");
            Assertions.owner(entity.getUserId(), userId);
            Assertions.isTrue(Objects.equals(entity.getStatus(), TaskStatusEnum.DOING.getValue()), "联盟豆增值任务不是进行中状态");
            list = List.of(entity);
        }
        for (CryptocurrencyIncreasingTaskDO it : list) {
            try {
                lock.lock();
                CryptocurrencyIncreasingTaskRecordDO taskRecordDO = cryptocurrencyIncreasingTaskRecordMapper.getOrCreateToday(userId, it.getId());
                // ！！！注意幂等性！！！存在任务提交记录则抛异常，阻止继续执行
                if (Boolean.TRUE.equals(taskRecordDO.getCompleted())) {
                    continue;
                }
                // 提交了看广告增值联盟豆的任务，则给广告计费
                Integer spendPrice = adPlanApi.updateSpendPriceWhenPlayComplete(param.getAdvertisingId());
                Long id = adEffectRecordApi.createIfNecessary(userId, AdEffectSceneEnum.CRYPTOCURRENCY_INCREMENT_SIGN, AdEffectTypeEnum.TASK_SUBMIT, param.getAdvertisingId(), param.getDuration(), spendPrice);
                if (id == null || id <= 0) {
                    continue;
                }
                cryptocurrencyIncreasingTaskRecordMapper.submitTodayTask(userId, it.getId());
                checkDailyTaskComplete(it, param.getAdvertisingId());
            } finally {
                lock.unlock();
            }
        }
    }

    private void checkDailyTaskComplete(CryptocurrencyIncreasingTaskDO aDo, Long adPlanId) {
        CryptocurrencyIncreasingTaskRecordDO taskRecordDO = cryptocurrencyIncreasingTaskRecordMapper.getOrCreateToday(aDo.getUserId(), aDo.getId());
        if (taskRecordDO.getCount() >= aDo.getRequireDailyTasks()) {
            cryptocurrencyIncreasingTaskRecordMapper.completeTodayTask(aDo.getUserId(), aDo.getId());
            CryptocurrencyIncreasingTaskDO updateObj = new CryptocurrencyIncreasingTaskDO();
            updateObj.setId(aDo.getId());
            updateObj.setCompleteDays(cryptocurrencyIncreasingTaskRecordMapper.countCompleteDays(aDo.getUserId(), aDo.getId()));
            getInjectedMapper().updateById(updateObj);
            // 完成每日任务，则发放每日增值的收益豆
            double increaseCryptocurrency = 1d * aDo.getInAmount() * aDo.getIncrementPercent() / 100d / aDo.getRequireDays();
            memberDigitalAssetsApi.increaseCryptocurrencyIncrement(aDo.getUserId(), increaseCryptocurrency);
            payBillApi.incomeAsync(new PayBillCreateDTO()
                    .setUserId(aDo.getUserId())
                    .setCategory(BillCategoryEnum.CRYPTOCURRENCY_INCREMENT)
                    .setType(BillTypeEnum.CRYPTOCURRENCY_INCREMENT)
                    .setNumber(increaseCryptocurrency)
                    .setMark("完成每日任务，发放【%.2f个】收益豆".formatted(increaseCryptocurrency))
                    .setRelateId(String.valueOf(aDo.getId()))
                    .setExtendField("{\"planId\":\"%s\"}".formatted(adPlanId)));
        }
    }

    @Override
    public List<CryptocurrencyIncreasingTaskCountBO> count(Long userId, TaskStatusEnum status) {
        return cryptocurrencyIncreasingTaskMapper.count(userId, status.getValue());
    }

    @Override
    public int autoCompleteOrExpire() {
        List<CryptocurrencyIncreasingTaskDO> list = getInjectedMapper().selectListByStatusAndLessThanExpireTime(
                TaskStatusEnum.DOING.getValue(), LocalDateTime.now());
        if (CollUtil.isEmpty(list)) {
            return 0;
        }
        int count = 0;
        for (CryptocurrencyIncreasingTaskDO recordDO : list) {
            try {
                // AOP：@Transactional 自调用问题
                boolean success = SpringUtil.getBean(getClass()).updateStatusAsExpire(recordDO);
                if (success) {
                    count++;
                }
            } catch (Exception e) {
                log.error("[autoCompleteOrExpire][CryptocurrencyIncreasingTask({}) 更新为已过期失败]", recordDO.getId(), e);
            }
        }
        return count;
    }

    @Transactional(rollbackFor = Exception.class)
    public boolean updateStatusAsExpire(CryptocurrencyIncreasingTaskDO recordDO) {
        memberDigitalAssetsApi.exchangeCryptocurrencyOriginToIncreasing(recordDO.getUserId(), 1d * recordDO.getInAmount());
        CryptocurrencyIncreasingTaskDO updateObj = new CryptocurrencyIncreasingTaskDO();
        updateObj.setId(recordDO.getId());
        if (Objects.equals(recordDO.getRequireDays(), recordDO.getCompleteDays())) {
            updateObj.setStatus(TaskStatusEnum.DONE.getValue());
        } else {
            updateObj.setStatus(TaskStatusEnum.EXPIRED.getValue());
        }
        return getInjectedMapper().updateById(updateObj) > 0;
    }

    @Override
    public int autoReturn() {
        List<CryptocurrencyIncreasingTaskSimpleBO> bos = cryptocurrencyIncreasingTaskMapper.selectAvailableUserIdAndTaskIds();
        for (CryptocurrencyIncreasingTaskSimpleBO bo : bos) {
            // 先确保今日任务记录存在，用户未领取过今日任务的情况下需创建今日任务记录
            cryptocurrencyIncreasingTaskRecordMapper.getOrCreateToday(bo.getUserId(), bo.getId());
        }
        List<CryptocurrencyIncreasingTaskRecordDO> list = cryptocurrencyIncreasingTaskRecordMapper.selectListByNeedReturned(LocalDate.now());
        if (CollUtil.isEmpty(list)) {
            return 0;
        }
        int count = 0;
        for (CryptocurrencyIncreasingTaskRecordDO entity : list) {
            try {
                // AOP：@Transactional 自调用问题
                boolean success = SpringUtil.getBean(getClass()).updateReturn(entity);
                if (success) {
                    count++;
                }
            } catch (Exception e) {
                log.error("[autoReturn][CryptocurrencyIncreasingTaskRecord({}) 更新为已发放失败]", entity.getId(), e);
            }
        }
        return count;
    }

    @Transactional(rollbackFor = Exception.class)
    public boolean updateReturn(CryptocurrencyIncreasingTaskRecordDO entity) {
        CryptocurrencyIncreasingTaskDO task = getInjectedMapper().selectById(entity.getTaskId());
        Assertions.notNull(task, "联盟豆增值任务不存在：" + entity.getTaskId());
        double returnCryptocurrency = 1d * task.getInAmount() / task.getRequireDays();
        memberDigitalAssetsApi.returnCryptocurrencyIncreasing(entity.getUserId(), returnCryptocurrency);
        CryptocurrencyIncreasingTaskRecordDO updateObj = new CryptocurrencyIncreasingTaskRecordDO();
        updateObj.setId(entity.getId());
        updateObj.setReturned(true);
        boolean b = cryptocurrencyIncreasingTaskRecordMapper.updateById(updateObj) > 0;
        if (b) {
            payBillApi.incomeAsync(new PayBillCreateDTO()
                    .setUserId(entity.getUserId())
                    .setCategory(BillCategoryEnum.CRYPTOCURRENCY_INCREMENT)
                    .setType(BillTypeEnum.CRYPTOCURRENCY_INCREMENT)
                    .setNumber(returnCryptocurrency)
                    .setMark("每日返还已增值的联盟豆本金【%.2f个】".formatted(returnCryptocurrency))
                    .setRelateId(String.valueOf(entity.getId()))
                    .setExtendField("{\"taskId\":\"%s\"}".formatted(task.getId())));
        }
        return b;
    }

}